﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.IO;
using System.Collections.ObjectModel;
using System.Windows.Forms;
using System.Threading;

namespace FileDuplicateUtility
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        internal static Dispatcher UIDispatcher = null;

        public MainWindow()
        {
            Progress = new Progress();
            Files = new ObservableCollection<File>();
            Duplicates = new ObservableCollection<DuplicateSet>();

            InitializeComponent();

            UIDispatcher = Dispatcher.CurrentDispatcher;

            Files.CollectionChanged += (s, e) => FileCount = Files.Count;
            Duplicates.CollectionChanged += (s, e) => DuplicateCount = Duplicates.Count;
        }

        public Progress Progress { get; private set; }
        public Folder RootFolder { get; private set; }
        public ObservableCollection<File> Files { get; private set; }
        public ObservableCollection<DuplicateSet> Duplicates { get; private set; }
        public int FileCount { get; private set; }
        public int DuplicateCount { get; private set; }

        public DirectoryInfo Root
        {
            get { return (DirectoryInfo)GetValue(RootProperty); }
            set { SetValue(RootProperty, value); }
        }

        public static readonly DependencyProperty RootProperty =
            DependencyProperty.Register("Root", typeof(DirectoryInfo), typeof(MainWindow), new UIPropertyMetadata(null));

        public string Status
        {
            get { return (string)GetValue(StatusProperty); }
            set { SetValue(StatusProperty, value); }
        }

        public static readonly DependencyProperty StatusProperty =
            DependencyProperty.Register("Status", typeof(string), typeof(MainWindow), new UIPropertyMetadata(string.Empty));

        internal void UpdateProgress(int? current = null, int? total = null, bool isIndeterminate = false)
        {
            if (UIDispatcher.CheckAccess())
            {
                if (total.HasValue)
                {
                    Progress.Total = total.Value;
                }

                if (current.HasValue)
                {
                    Progress.Current = current.Value;
                }

                Progress.IsIndeterminate = isIndeterminate;
            }
            else
            {
                UIDispatcher.Invoke((Action)(() => UpdateProgress(current, total, isIndeterminate)));
            }
        }

        internal void UpdateStatus(Func<string> action)
        {
            if (UIDispatcher.CheckAccess())
            {
                Status = action();
            }
            else
            {
                UIDispatcher.Invoke((Action)(() => UpdateStatus(action)));
            }
        }

        internal void UpdateStatus(string format, params object[] args)
        {
            UpdateStatus(() => string.Format(format, args));
        }

        private void AddFiles(IEnumerable<File> files)
        {
            if (UIDispatcher.CheckAccess())
            {
                foreach (var file in files)
                {
                    Files.Add(file);
                }
            }
            else
            {
                UIDispatcher.BeginInvoke((Action)(() => AddFiles(files)));
            }
        }

        private void StartBuildFolderStructure(DirectoryInfo dir)
        {
            RootFolder = BuildFolderStructure(dir);
            RootFolder.Name = dir.FullName;

            UpdateStatus("Scanning Complete! {0} Files Discovered", FileCount);

            BuildDuplicateList();

            UpdateStatus("Duplicate Scan Complete! {0} Duplicates Discovered", DuplicateCount);
        }

        private Folder BuildFolderStructure(DirectoryInfo dir, Folder parent = null)
        {
            var folder = new Folder(dir.Name, parent);

            UpdateStatus("Scanning Files of {0}", folder.Name);

            folder.AddFiles(dir.GetFiles().Select(x => new File(folder, x.Name, x.Length)));

            AddFiles(folder.Files);

            UpdateStatus("Scanning Folders of {0}", folder.Name);

            folder.AddFolders(dir.GetDirectories().Select(x => BuildFolderStructure(x, folder)));

            return folder;
        }

        private void AddDuplicate(DuplicateSet dupes)
        {
            if (UIDispatcher.CheckAccess())
            {
                Duplicates.Add(dupes);
            }
            else
            {
                UIDispatcher.Invoke((Action)(() => AddDuplicate(dupes)));
            }
        }

        private void BuildDuplicateList()
        {
            UpdateStatus("Scanning For Duplicates...");
            UpdateProgress(0, FileCount);

            DuplicateSet dupes = null;
            var enumerator = Files.OrderBy(x => x.Size).GetEnumerator();
            var count = 0;

            if (enumerator.MoveNext())
            {
                var a = enumerator.Current;
                UpdateProgress(++count);

                while (enumerator.MoveNext())
                {
                    var b = enumerator.Current;
                    UpdateProgress(++count);

                    if (CheckDuplicate(a, b))
                    {
                        if (dupes == null)
                        {
                            
                            UIDispatcher.Invoke((Action)(() =>
                            {
                                dupes = new DuplicateSet(a, b);
                                AddDuplicate(dupes);
                            }));
                        }
                        else
                        {
                            UIDispatcher.Invoke((Action)(() => dupes.AddFile(b)));
                        }
                    }
                    else
                    {
                        a = b;
                        dupes = null;
                    }
                }
            }
        }

        private bool CheckDuplicate(File a, File b)
        {
            var isDupe = false;

            if (a.Size == b.Size)
            {
                isDupe = true;
            }

            return isDupe;
        }

        private void Browse_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            DirectoryInfo dir = null;

            using (var fbd = new FolderBrowserDialog())
            {
                fbd.Description = "Select The Root Folder to Scan";
                fbd.ShowNewFolderButton = false;

                if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    dir = new DirectoryInfo(fbd.SelectedPath);
                }
            }

            if (dir != null)
            {
                Progress.Reset();
                UpdateProgress(isIndeterminate: true);
                UpdateStatus("Initiating File Scan...");
                Files.Clear();
                Duplicates.Clear();
                Root = dir;

                ThreadPool.QueueUserWorkItem(state => StartBuildFolderStructure(dir));
            }
        }

        private void CheckAllCmd_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            CheckAllAction action;
            if (Enum.TryParse<CheckAllAction>(e.Parameter.ToString(), out action))
            {
                if (action == CheckAllAction.None)
                {
                    foreach (var file in Duplicates.SelectMany(x => x.Files))
                    {
                        file.IsChecked = false;
                    }
                }
                else
                {
                    foreach (var file in Duplicates.SelectMany(x => x.Files))
                    {
                        file.IsChecked = true;
                    }
                }
            }
        }

        private void CreateCheckedFileFilterCmd_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            var filter = new CheckedFileFilter(this);
            filter.Show();
        }

        private void MoveCheckedFilesCmd_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            MoveFilesAction action;

            if (Enum.TryParse<MoveFilesAction>(e.Parameter.ToString(), out action))
            {
                using (var fbd = new FolderBrowserDialog())
                {
                    fbd.Description = "Select Destination Folder";
                    fbd.ShowNewFolderButton = true;

                    if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                    {
                        UpdateProgress(isIndeterminate: true);
                        UpdateStatus("Preparing to Move Files...");

                        ThreadPool.QueueUserWorkItem(state => MoveCheckedFiles(action, fbd.SelectedPath));
                    }
                }
            }
        }

        private void MoveCheckedFiles(MoveFilesAction action, string basePath)
        {
            File[] files = null;

            UIDispatcher.Invoke((Action)(() => files = Duplicates.SelectMany(x => x.CheckedFiles).ToArray()));

            int current = 0;

            UpdateProgress(current, files.Length);

            foreach (var file in files)
            {
                switch (action)
                {
                    case MoveFilesAction.Move:
                        UpdateStatus("Moving {0}...", file.Name);
                        file.FileInfo.MoveTo(System.IO.Path.Combine(basePath, file.Name));
                        break;
                    case MoveFilesAction.Copy:
                        UpdateStatus("Copying {0}...", file.Name);
                        file.FileInfo.CopyTo(System.IO.Path.Combine(basePath, file.Name));
                        break;
                }
                UpdateProgress(++current);
            }

            UpdateStatus("{0} Files Moved to {1}", current, basePath);
        }

        public enum CheckAllAction
        {
            All,
            None
        }

        public enum MoveFilesAction
        {
            Move,
            Copy
        }
    }

    public class DebugConverter : IValueConverter
    {
        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return value;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return value;
        }

        #endregion
    }
}
